package service

import (
	"bytes"
	"errors"
	"gin_gorm_oj/define"
	"gin_gorm_oj/helper"
	"gin_gorm_oj/model"
	"github.com/gin-gonic/gin"
	"gorm.io/gorm"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os/exec"
	"runtime"
	"strconv"
	"sync"
	"time"
)

// GetSubmitList
// @Tags 公共方法
// @Summary 提交列表
// @Param page query int false "page"
// @Param size query int false "size"
// @Param problem_id query string false "problem_id"
// @Param user_id query string false "user_id"
// @Param status query int false "status"
// @Success 200 {string} json "{"code":"200","msg","","data":""}"
// @Router /submitList [get]
func GetSubmitList(c *gin.Context) {
	page, _ := strconv.Atoi(c.DefaultQuery("page", define.DefaultPage))
	size, _ := strconv.Atoi(c.DefaultQuery("size", define.DefaultPageSize))
	problem_id := c.Query("problem_id")
	user_id := c.Query("user_id")
	status, _ := strconv.Atoi(c.Query("status"))
	list := make([]*model.Submit, 0)
	var count int64
	tx := model.DB.Model(new(model.Submit)).Preload("Problem").Preload("User")
	if problem_id != "" {
		tx = tx.Where("problem_id = ?", problem_id)
	}
	if user_id != "" {
		tx = tx.Where("user_id = ?", user_id)
	}
	if status != 0 {
		tx = tx.Where("status = ?", status)
	}
	// 分页	偏移量
	err := tx.Count(&count).Offset((page - 1) * size).Limit(size).Find(&list).Error
	if err != nil {
		log.Println("GetSubmitList err:", err)
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"code": 200,
		"msg":  "success",
		"data": gin.H{
			"list":  list,
			"count": count,
			"page":  page,
			"size":  size,
		},
	})
}

// Submit
// @Tags 用户私有方法
// @Summary 代码提交
// @Param authorization header string true "authorization"
// @Param problem_identity query string true "problem_identity"
// @Param code body string true "code"
// @Success 200 {string} json "{"code":"200","msg","","data":""}"
// @Router /user/submit [post]
func Submit(c *gin.Context) {
	problemIdentity := c.Query("problem_identity")
	code, err := ioutil.ReadAll(c.Request.Body)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"code": -1,
			"msg":  "获取代码错误",
		})
		return
	}
	//代码保存
	path, err := helper.SaveCode(code)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"code": -1,
			"msg":  "获取代码错误",
		})
		return
	}
	//提交
	user, _ := c.Get("user")
	userClaims := user.(*helper.UserClaims)
	submit := &model.Submit{
		Identity:  helper.GetUUID(),
		ProblemId: problemIdentity,
		UserId:    userClaims.UserID,
		Path:      path,
	}
	//代码判断
	pb := new(model.Problem)
	err = model.DB.Where("identity = ?", problemIdentity).Preload("TestCase").First(pb).Error
	if err != nil {
		log.Println("Submit err:", err)
		c.JSON(http.StatusOK, gin.H{
			"code": -1,
			"msg":  "题目不存在",
		})
		return
	}
	WA := make(chan int)  //答案错误
	OOM := make(chan int) //内存超限
	CE := make(chan int)  //编译错误
	passCount := 0        //通过数量
	var lock sync.Mutex
	var msg string //错误信息
	for _, testcase := range pb.TestCases {
		// 代码判题
		go func() {
			//执行代码
			cmd := exec.Command("go", "run", path)
			var stdout, stderr bytes.Buffer
			cmd.Stdout = &stdout
			cmd.Stderr = &stderr
			stdinPipe, err := cmd.StdinPipe()
			if err != nil {
				log.Fatal("Error creating stdin pipe:", err)
			}
			io.WriteString(stdinPipe, testcase.Input)
			//运行代码
			var bm runtime.MemStats   // 记录内存分配情况
			runtime.ReadMemStats(&bm) // 读取当前内存分配情况
			if outErr := cmd.Run(); outErr != nil {
				log.Println("Error running command:", stderr.String())
				if err.Error() == "exit status 2" {
					msg = stderr.String()
					CE <- 1
					return
				}
			}
			var em runtime.MemStats
			runtime.ReadMemStats(&em) // 读取当前内存分配情况
			//答案错误
			if stdout.String() != testcase.Output {
				msg = "答案错误"
				WA <- 1
				return
			}
			//内存超出
			if (em.Alloc/1024)-(bm.Alloc/1024) > uint64(pb.MaxMemory) {
				msg = "内存超出"
				OOM <- 1
				return
			}
			//通过数量加一
			lock.Lock()
			passCount++
			lock.Unlock()

		}()
	}
	select {
	case <-WA:
		submit.Status = 2
		msg = "答案错误"
	case <-OOM:
		submit.Status = 4
		msg = "内存超出"
	case <-time.After(time.Microsecond * time.Duration(pb.MaxRuntime)):
		if passCount == len(pb.TestCases) {
			submit.Status = 1
			msg = "答案正确"
		} else {
			submit.Status = 3
			msg = "运行超时"
		}
	case <-CE:
		submit.Status = 5
		msg = "编译错误"
	}
	if err = model.DB.Transaction(func(tx *gorm.DB) error {
		err = tx.Create(submit).Error
		if err != nil {
			return errors.New("保存提交记录失败:" + err.Error())
		}
		m := make(map[string]interface{})
		m["submit_num"] = gorm.Expr("submit_num + ?", 1)
		if submit.Status == 1 {
			m["pass_num"] = gorm.Expr("pass_num + ?", 1)
		}
		//更新用户做题数量
		err = tx.Model(new(model.User)).Where("identity = ?", userClaims.UserID).Updates(m).Error
		if err != nil {
			return errors.New("更新用户做题数量错误:" + err.Error())
		}
		//更新问题
		err = tx.Model(new(model.Problem)).Where("identity = ?", problemIdentity).Updates(m).Error
		if err != nil {
			return errors.New("更新问题数量错误:" + err.Error())
		}
		return nil
	}); err != nil {
		c.JSON(http.StatusOK, gin.H{
			"code": -1,
			"msg":  "提交失败",
		})
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"code": 200,
		"data": map[string]interface{}{
			"msg":    msg,
			"status": submit.Status,
		},
	})
}
